package rexsee.dialog;

import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;

public class ImageMultiTouchListener implements OnTouchListener {

	private static final int MODE_NONE = 0;
	private static final int MODE_DRAG = 1;
	private static final int MODE_SCALE = 2;

	private int mode = MODE_NONE;
	private float scaleStart;

	private final ImageView view;
	private final Matrix matrix = new Matrix();
	private final Matrix savedMatrix = new Matrix();
	private final PointF dragStart = new PointF();
	private final PointF scaleCenter = new PointF();

	private boolean hasOriginal = false;
	private float originalScale = 1;
	private float originalTranslateX = 0;
	private float originalTranslateY = 0;

	private final Handler animationHandler = new Handler();
	private Runnable animationRunnable;
	private final int animationSteps = 10;
	private final int animationStepCycle = 10;
	private int animationStepCount = 0;

	private final int drawableWidth;
	private final int drawableHeight;
	private final int viewWidth;
	private final int viewHeight;

	private final int mDistanceThreshold;
	private final OnImageDragedOverThresholdListener mDragedListener;

	public static abstract class OnImageDragedOverThresholdListener {
		public static final int DIRECTION_UP = 0;
		public static final int DIRECTION_DOWN = 1;
		public static final int DIRECTION_LEFT = 2;
		public static final int DIRECTION_RIGHT = 3;
		public abstract void dragedOverThreshold(int direction);
	}

	public ImageMultiTouchListener(ImageView view, int distanceThreshold, OnImageDragedOverThresholdListener dragListener) {
		this(view, -1, -1, distanceThreshold, dragListener);
	}
	public ImageMultiTouchListener(ImageView view, int width, int height, int distanceThreshold, OnImageDragedOverThresholdListener dragListener) {
		this.view = view;
		mDistanceThreshold = distanceThreshold;
		mDragedListener = dragListener;
		view.setClickable(true);
		viewWidth = (width > 0) ? width : view.getWidth();
		viewHeight = (height > 0) ? height : view.getHeight();
		Drawable drawable = view.getDrawable();
		if (drawable == null || !(drawable instanceof BitmapDrawable)) {
			drawableWidth = 0;
			drawableHeight = 0;
			return;
		}
		drawableWidth = drawable.getIntrinsicWidth();
		drawableHeight = drawable.getIntrinsicHeight();
		ScaleType scaleType = view.getScaleType();
		view.setScaleType(ScaleType.MATRIX);
		if (scaleType.equals(ImageView.ScaleType.CENTER)) {
		} else if (scaleType.equals(ImageView.ScaleType.CENTER_CROP)) {
		} else if (scaleType.equals(ImageView.ScaleType.CENTER_INSIDE)) {
			hasOriginal = true;
			originalScale = Math.min((float) viewWidth / (float) drawableWidth, (float) viewHeight / (float) drawableHeight);
			originalTranslateX = (viewWidth - drawableWidth * originalScale) / 2;
			originalTranslateY = (viewHeight - drawableHeight * originalScale) / 2;
			if (originalScale > 0) matrix.postScale(originalScale, originalScale, 0, 0);
			matrix.postTranslate(originalTranslateX, originalTranslateY);
			view.setImageMatrix(matrix);
		} else if (scaleType.equals(ImageView.ScaleType.FIT_CENTER)) {
		} else if (scaleType.equals(ImageView.ScaleType.FIT_START)) {
		} else if (scaleType.equals(ImageView.ScaleType.FIT_END)) {
		} else if (scaleType.equals(ImageView.ScaleType.FIT_XY)) {
		} else {
		}
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {

		if (!(v instanceof ImageView)) return false;
		ImageView view = (ImageView) v;

		switch (event.getAction() & MotionEvent.ACTION_MASK) {
			case MotionEvent.ACTION_DOWN :
				savedMatrix.set(matrix);
				dragStart.set(event.getX(), event.getY());
				mode = MODE_DRAG;
				break;
			case MotionEvent.ACTION_POINTER_DOWN :
				scaleStart = spacing(event);
				if (scaleStart > 10f) {
					savedMatrix.set(matrix);
					midPoint(scaleCenter, event);
					mode = MODE_SCALE;
				}
				break;
			case MotionEvent.ACTION_UP :
				float[] values = new float[9];
				matrix.getValues(values);
				if (hasOriginal) {
					if (values[Matrix.MSCALE_X] < originalScale) {
						restore();
					} else {
						align();
					}
				}
			case MotionEvent.ACTION_POINTER_UP :
				mode = MODE_NONE;
				break;
			case MotionEvent.ACTION_MOVE :
				if (mode == MODE_DRAG) {
					matrix.set(savedMatrix);
					float tx = event.getX() - dragStart.x;
					float ty = event.getY() - dragStart.y;
					matrix.postTranslate(tx, ty);
				} else if (mode == MODE_SCALE) {
					float newDist = spacing(event);
					if (newDist > 10f) {
						matrix.set(savedMatrix);
						float scale = newDist / scaleStart;
						matrix.postScale(scale, scale, scaleCenter.x, scaleCenter.y);
					}
				}
				break;
		}

		view.setImageMatrix(matrix);
		return false;
	}

	private float spacing(MotionEvent event) {
		float x = event.getX(0) - event.getX(1);
		float y = event.getY(0) - event.getY(1);
		return FloatMath.sqrt(x * x + y * y);
	}

	private void midPoint(PointF point, MotionEvent event) {
		float x = event.getX(0) + event.getX(1);
		float y = event.getY(0) + event.getY(1);
		point.set(x / 2, y / 2);
	}

	public void align() {
		final float[] values = new float[9];
		matrix.getValues(values);
		float targetTranslateX = values[Matrix.MTRANS_X];
		float targetTranslateY = values[Matrix.MTRANS_Y];
		float width = drawableWidth * values[Matrix.MSCALE_X];
		float height = drawableHeight * values[Matrix.MSCALE_Y];
		if (width > viewWidth) {
			if (values[Matrix.MTRANS_X] > 0) targetTranslateX = 0;
			else if ((values[Matrix.MTRANS_X] + width) < viewWidth) targetTranslateX = viewWidth - width;
		} else {
			float midX = (viewWidth - width) / 2;
			if (values[Matrix.MTRANS_X] > midX || (values[Matrix.MTRANS_X] + width) < viewWidth - midX) {
				targetTranslateX = midX;
			}
		}
		if (height > viewHeight) {
			if (values[Matrix.MTRANS_Y] > 0) targetTranslateY = 0;
			else if ((values[Matrix.MTRANS_Y] + height) < viewHeight) targetTranslateY = viewHeight - height;
		} else {
			float midY = (viewHeight - height) / 2;
			if (values[Matrix.MTRANS_Y] > midY || (values[Matrix.MTRANS_Y] + height) < viewHeight - midY) {
				targetTranslateY = midY;
			}
		}
		if (targetTranslateX == values[Matrix.MTRANS_X] && targetTranslateY == values[Matrix.MTRANS_Y]) return;
		float translateXOffset = targetTranslateX - values[Matrix.MTRANS_X];
		float translateYOffset = targetTranslateY - values[Matrix.MTRANS_Y];
		if (mDistanceThreshold > 0 && mDragedListener != null) {
			if (translateXOffset < -mDistanceThreshold) mDragedListener.dragedOverThreshold(OnImageDragedOverThresholdListener.DIRECTION_LEFT);
			if (translateXOffset > mDistanceThreshold) mDragedListener.dragedOverThreshold(OnImageDragedOverThresholdListener.DIRECTION_RIGHT);
			if (translateYOffset < -mDistanceThreshold) mDragedListener.dragedOverThreshold(OnImageDragedOverThresholdListener.DIRECTION_UP);
			if (translateYOffset > mDistanceThreshold) mDragedListener.dragedOverThreshold(OnImageDragedOverThresholdListener.DIRECTION_DOWN);
		}
		final float stepXOffset = translateXOffset / animationSteps;
		final float stepYOffset = translateYOffset / animationSteps;
		animationStepCount = 0;
		animationRunnable = new Runnable() {
			@Override
			public void run() {
				if (animationStepCount == animationSteps) {
					animationStepCount = 0;
					return;
				} else {
					animationStepCount++;
					try {
						matrix.postTranslate(stepXOffset, stepYOffset);
						view.setImageMatrix(matrix);
						animationHandler.postDelayed(animationRunnable, animationStepCycle);
					} catch (Exception e) {
					}
				}
			}
		};
		animationHandler.postDelayed(animationRunnable, animationStepCycle);
	}
	public void restore() {
		final float[] values = new float[9];
		matrix.getValues(values);
		final float scaleOffset = (originalScale - values[Matrix.MSCALE_X]) / animationSteps;
		final float translateXOffset = (originalTranslateX - values[Matrix.MTRANS_X]) / animationSteps;
		final float translateYOffset = (originalTranslateY - values[Matrix.MTRANS_Y]) / animationSteps;
		animationStepCount = 0;
		animationRunnable = new Runnable() {
			@Override
			public void run() {
				if (animationStepCount == animationSteps) {
					animationStepCount = 0;
					return;
				} else {
					animationStepCount++;
					try {
						matrix.reset();
						float scale = values[Matrix.MSCALE_X] + scaleOffset * animationStepCount;
						float tx = values[Matrix.MTRANS_X] + translateXOffset * animationStepCount;
						float ty = values[Matrix.MTRANS_Y] + translateYOffset * animationStepCount;
						matrix.postScale(scale, scale, 0, 0);
						matrix.postTranslate(tx, ty);
						view.setImageMatrix(matrix);
						animationHandler.postDelayed(animationRunnable, animationStepCycle);
					} catch (Exception e) {
					}
				}
			}
		};
		animationHandler.postDelayed(animationRunnable, animationStepCycle);
	}

	public boolean isOriginal() {
		final float[] values = new float[9];
		matrix.getValues(values);
		float scaleOffset = originalScale - values[Matrix.MSCALE_X];
		return scaleOffset >= 0 && mode != MODE_SCALE;
	}

}
